﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections;

namespace Noris.Schedule.Support
{
	/// <summary>
	/// Brána do systému.
	/// Bránu lze instancovat i zvenku (pokud ji zvenku dokážeme najít).
	/// Do konstruktoru lze předat standardní parametry jaké se předávají při spouštění scheduleru (=parametry aplikace), ve formě jednotlivých položek "Name=Value".
	/// Brána je určena pro spouštění funkcí Cowley (musí být vestavěny v některé z DLL scheduleru).
	/// Pro spouštění funkcí slouží metody RunCowleySync() - čeká na dokončení funkce, a pak vrací řízení; 
	/// a RunCowleyAsync() - nečeká na dokončení, vrací řízení okamžitě.
	/// Parametrem metod je jednak název funkce (CowleyID), a jednak odkaz na metodu, která dostává informace o 
	/// </summary>
	public class Gate : ICallBackTarget, IDisposable
	{
		/// <summary>
		/// Konstruktor: přebírástandardní sadu argumentů scheduleru (=parametry aplikace), ve formě jednotlivých položek "Name=Value".
		/// </summary>
		/// <param name="arguments"></param>
		public Gate(params string[] arguments)
		{
			Steward.SetSupportPathAsDefault();

			// Parametry aplikace:
			Steward.ParameterStore(arguments);
			Steward.AppInit();

			this._WaitForDone = new AutoResetEvent(false);
			this._ProgressCalc = new ProgressCalc(this._ProgressValueChanged);
		}
		/// <summary>
		/// Spustí funkci, která je specifikovaná v parametrech aplikace.
		/// Spouští ji synchronně = metoda vrací řízení až poté, kdy funkce skončí.
		/// </summary>
		public void RunCowleySync()
		{
			Cowley.RunCowley(Steward.CowleyIdFromParams, this as ICallBackTarget);
			this._WaitToDone();
		}
		/// <summary>
		/// Spustí funkci, která je specifikovaná v parametrech aplikace.
		/// Spouští ji asynchronně = metoda vrací řízení ihned poté, kdy je funkce spuštěna.
		/// </summary>
		public void RunCowleyAsync()
		{
			Cowley.RunCowley(Steward.CowleyIdFromParams, this as ICallBackTarget);
		}
		/// <summary>
		/// Spustí funkci, která je daná parametrem této metody.
		/// Spouští ji synchronně = metoda vrací řízení až poté, kdy funkce skončí.
		/// </summary>
		/// <param name="cowleyId">Identifikace funkce</param>
		public void RunCowleySync(string cowleyId)
		{
			Cowley.RunCowley(cowleyId, this as ICallBackTarget);
			this._WaitToDone();
		}
		/// <summary>
		/// Spustí funkci, která je daná parametrem této metody.
		/// Spouští ji asynchronně = metoda vrací řízení ihned poté, kdy je funkce spuštěna.
		/// </summary>
		/// <param name="cowleyId">Identifikace funkce</param>
		public void RunCowleyAsync(string cowleyId)
		{
			Cowley.RunCowley(cowleyId, this as ICallBackTarget);
		}
		/// <summary>
		/// Spustí funkci, která je daná parametrem této metody.
		/// Spouští ji synchronně = metoda vrací řízení až poté, kdy funkce skončí.
		/// Volající předává referenci na svoji metodu, která je volaná po každé změně progresu výkonné funkce.
		/// Tato cílová metoda má přebírat jeden parametr typu System.Collections.Hashtable, která obsahuje klíč (string) a odpovídající hodnotu.
		/// Klíče (a hodnoty):
		/// title (titulek funkce), text (text k progresu), resultFailed (výjimka popisující chybu), resultSucceed (nic), position (decimal ukazatel v rozmezí 0 až 1).
		/// </summary>
		/// <param name="cowleyId">Identifikace funkce</param>
		/// <param name="progressChanged">Odkaz na metodu, která řeší progres</param>
		public void RunCowleySync(string cowleyId, Action<Hashtable> progressChanged)
		{
			this.ProgressChanged = progressChanged;
			Cowley.RunCowley(cowleyId, this as ICallBackTarget);
			this._WaitToDone();
			this.ProgressChanged = null;           // Zde lze zrušit referenci na metodu progres, protože funkce (proces) už skončila.
		}
		/// <summary>
		/// Spustí funkci, která je daná parametrem této metody.
		/// Spouští ji asynchronně = metoda vrací řízení ihned poté, kdy je funkce spuštěna.
		/// Volající předává referenci na svoji metodu, která je volaná po každé změně progresu výkonné funkce.
		/// Tato cílová metoda má přebírat jeden parametr typu System.Collections.Hashtable, která obsahuje klíč (string) a odpovídající hodnotu.
		/// Klíče (a hodnoty):
		/// title (titulek funkce), text (text k progresu), resultFailed (výjimka popisující chybu), resultSucceed (nic), position (decimal ukazatel v rozmezí 0 až 1).
		/// </summary>
		/// <param name="cowleyId">Identifikace funkce</param>
		/// <param name="progressChanged">Odkaz na metodu, která řeší progres</param>
		public void RunCowleyAsync(string cowleyId, Action<Hashtable> progressChanged)
		{
			this.ProgressChanged = progressChanged;
			Cowley.RunCowley(cowleyId, this as ICallBackTarget);
			// this.ProgressChanged = null;        Toto nelze, protože objekt dále existuje, a volá svůj progres - i když řízení odsud už odešlo!!!
		}
		/// <summary>
		/// Odkaz na metodu, která se má volat při jakékoli změně progresu v tomto objektu.
		/// Typicky jde o parametr metod RunCowleySync(), RunCowleyAsync().
		/// </summary>
		public Action<Hashtable> ProgressChanged;
		/// <summary>
		/// Zajistí vyvolání události this.ProgressChanged, s předáním dat. Jde o volání do nadřízené vrstvy, ven z this objektu.
		/// </summary>
		/// <param name="key"></param>
		/// <param name="value"></param>
		protected void OnProgressChanged(string key, object value)
		{
			if (this.ProgressChanged != null)
			{
				Hashtable ht = new Hashtable();
				ht.Add(key, value);
				this.ProgressChanged(ht);
			}
		}
		/// <summary>
		/// Objekt, který zajišťuje kalkulace progresu (segmenty, přepočty, event this._ProgressValueChanged)
		/// </summary>
		private ProgressCalc _ProgressCalc;
		/// <summary>
		/// Čekání na dokončení výkonné části
		/// </summary>
		private void _WaitToDone()
		{
			this._WaitForDone.Reset();
			this._WaitForDone.WaitOne();
		}
		/// <summary>
		/// Objekt, který umožní jednomu vláknu efektivně čekat (bez spotřeby výkonu) na signál z jiného vlákna
		/// </summary>
		private AutoResetEvent _WaitForDone;
		void IDisposable.Dispose()
		{
			Steward.AppDone();
		}
		/// <summary>
		/// Obsluha události z výkonného procesu
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="args"></param>
		void ICallBackTarget.CallTarget(object sender, CallBackTargetArgs args)
		{
			// Objekt progresu si zpracuje svoje stavy (CreateSegment a DoProgress), a vypočte novou (absolutní) hodnotu progresu, poté vyvolá event => this._ProgressValueChanged()
			this._ProgressCalc.CallProgress(args);

			switch (args.ProcessState)
			{
				case CallBackProcessState.SetTitle:
					this.OnProgressChanged("title", args.FormTitle);
					break;
				case CallBackProcessState.ShowText:
				case CallBackProcessState.DoProgress:
					if (args.TextLines != null)
						this.OnProgressChanged("text", args.TextLines);
					break;
				case CallBackProcessState.Failed:
					this.OnProgressChanged("resultFailed", args.Exc);
					this._WaitForDone.Set();                      // Pošlu signál do čekajícího objektu, že se dočkal.
					break;
				case CallBackProcessState.Succeed:
					this.OnProgressChanged("resultSucceed", null);
					this._WaitForDone.Set();                      // Pošlu signál do čekajícího objektu, že se dočkal.
					break;
			}
		}
		/// <summary>
		/// Událost, kdy došlo ke změně hodnoty progresu (dostává aktuální hodnotu 0 až 1)
		/// </summary>
		/// <param name="value"></param>
		void _ProgressValueChanged(decimal value)
		{
			// Upozornění: v této chvíli ještě NEMUSÍ být přítomen objekt třídy ProgressCalc v proměnné this._ProgressCalc !!!
			// Jakto, když se tam přímo v konstruktoru vkládá?
			// V našem konstruktoru je příkaz: 
			//    this._ProgressCalc = new ProgressCalc(this._ProgressValueChanged);
			// ale tím se vyvolává konstruktor třídy ProgressCalc, a ten inicializuje svoje data, čehož součástí je inicializace hodnoty,
			// a tím dojde k prvnímu vyvolání metody ProgressValueChanged (to je tato).
			// Teprve po skončení konstruktoru třídy ProgressCalc je vytvořený objekt uložen do this._ProgressCalc,
			//  takže první volání této metody nemá k dispozici this._ProgressCalc !
			// Jako hodnotu využívám předanou hodnotu value:
			this.OnProgressChanged("position", value);
		}
	}
}
